home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Environments / Oberon⁄F™ 1.2 / Preinstalled version / Manuals / Commands (.txt) < prev    next >
Encoding:
Oberon Document  |  1996-07-08  |  158.2 KB  |  647 lines  |  [oODC/obnF]

  1. Documents.StdDocumentDesc
  2. Documents.DocumentDesc
  3. Containers.ViewDesc
  4. Views.ViewDesc
  5. Stores.StoreDesc
  6. Documents.ModelDesc
  7. Containers.ModelDesc
  8. Models.ModelDesc
  9. Stores.ElemDesc
  10. TextViews.StdViewDesc
  11. TextViews.ViewDesc
  12. TextModels.StdModelDesc
  13. TextModels.ModelDesc
  14. TextModels.AttributesDesc
  15. Helvetica
  16. TextRulers.StdRulerDesc
  17. TextRulers.RulerDesc
  18. TextRulers.StdStyleDesc
  19. TextRulers.StyleDesc
  20. TextRulers.AttributesDesc
  21. StdLogos.ViewDesc
  22. Helvetica
  23. Helvetica
  24. Helvetica
  25. StdFolds.FoldDesc
  26. Helvetica
  27. StdLinks.LinkDesc
  28. StdCmds.OpenBrowser('Manuals/Copyrigh', 'Copyright')
  29. Helvetica
  30. Oberon/F Release 1.2
  31. Copyright 
  32.  1994-1996 by Oberon microsystems, Inc., Switzerland.
  33. All rights reserved. No part of this publication may be reproduced in any form or by any means, without prior written permission by Oberon microsystems. The only exception is the free electronic distribution of the education version of Oberon/F (see the accompanying 
  34. copyright
  35. notice
  36.  for details).
  37. Oberon/F module interfaces and their descriptions in particular may not be used in other works without written permission.
  38. Oberon microsystems, Inc.
  39. Technoparkstrasse 1
  40. CH-8005 Z
  41. Switzerland
  42. Oberon is a trademark of ETH Z
  43. rich, Switzerland.
  44. Oberon/F, Oberon/L, "Oberon by Example", "The Oberon Tribune", "Oberon Developer Forum", and "Drag & Pick" are trademarks of Oberon microsystems, Inc.
  45. All other trademarks and registered trademarks belong to their respective owners.
  46. Helvetica
  47. StdLinks.ShowTarget('Introduction')
  48. Helvetica
  49. StdLinks.ShowTarget('Texts')
  50. StdLinks.ShowTarget('Forms')
  51. StdLinks.ShowTarget('Commands')
  52. StdLinks.ShowTarget('Files')
  53. Helvetica
  54. StdLinks.TargetDesc
  55. Introduction
  56. Helvetica
  57. Texts
  58. Helvetica
  59. HostPictures.StdViewDesc
  60. Geneva
  61. position 3
  62. position 0
  63. Helvetica
  64. Forms
  65. Helvetica
  66. Controls.PushButtonDesc
  67. Controls.ControlDesc
  68. Dialog.Beep
  69. StdCmds.OpenDoc('Obx/Docu/Buttons')
  70. Geneva
  71. DevCommanders.StdViewDesc
  72. DevCommanders.ViewDesc
  73. FormViews.StdViewDesc
  74. FormViews.ViewDesc
  75. FormModels.StdModelDesc
  76. FormModels.ModelDesc
  77. Controls.CaptionDesc
  78. ObxForms4.para.limit
  79. limit
  80. Controls.FieldDesc
  81. Controls.ControlDesc
  82. Views.ViewDesc
  83. Stores.StoreDesc
  84. ObxForms3.para.limit
  85. ObxForms3.NotifyLimit
  86. ObxForms3.para.sum
  87. ObxForms3.para.sum
  88. ObxForms3.ReadOnlyGuard
  89. FormControllers.StdControllerDesc
  90. FormControllers.ControllerDesc
  91. Containers.ControllerDesc
  92. Controllers.ControllerDesc
  93. TextModels.AttributesDesc
  94. Chicago
  95. Chicago
  96. Chicago
  97. ObxForms3.para.limit
  98. ObxForms3.NotifyLimit
  99. ObxForms3.para.sum
  100. ObxForms3.ReadOnlyGuard
  101. ObxForms2.NotifyLimit
  102. TextRulers.StdRulerDesc
  103. TextRulers.RulerDesc
  104. TextRulers.StdStyleDesc
  105. TextRulers.StyleDesc
  106. Models.ModelDesc
  107. Stores.ElemDesc
  108. TextRulers.AttributesDesc
  109. StdLinks.TargetDesc
  110. Commands
  111. Helvetica
  112. Helvetica
  113. Helvetica
  114. Files
  115. HostPictures.StdViewDesc
  116. Geneva
  117. riders
  118. at beginning
  119. at pos = 5
  120. at end
  121.  Oberon/F
  122. Command Tutorial
  123. Copyright Notice
  124. Contents
  125. Introduction
  126. Working
  127. Texts
  128. Working
  129. Forms
  130. Working
  131. Commands
  132. Working
  133. Files
  134. Introduction
  135. In Oberon/F, two major levels of programming can be distinguished.
  136. The simplest level is the programming of commands. A command is a procedure that performs one or several operations like opening a window, pasting a text, stretching or selecting parts of a graph. Commands are usually invoked interactively by the user, e.g. by selecting a menu entry or by clicking on a button, and they typically operate on abstract data types, e.g. on texts or on forms.
  137. Command programming is sometimes called scripting, or component integration.
  138. The second level of programming is the programming of views. A view provides a visual presentation of data, e.g. in a window or as part of another view. Views can be stored and printed, and their contents can be edited interactively, e.g. through cut, copy, drag and drop, etc.
  139. View programming is sometimes called component creation.
  140. In this tutorial, there is one chapter on command programming, and another chapter on view programming.
  141. This tutorial introduces the most fundamental aspects of Oberon/F by means of graduated examples. It doesn't attempt to show all features supported by Oberon/F. Your copy of the software contains additional documentation on-line, e.g. commented sources of views or examples highlighting more specialized aspects of the framework. For a complete and systematic presentation of the Oberon/F interfaces, the reader is referred to the reference part of the on-line documentation.
  142. This chapter presents several simple example programs, which demonstrate how the standard text and form abstractions can be used from within a program.
  143. Working with Texts
  144. When using a new programming environment, many programmers first write a program which puts the string "Hello World" onto the screen. In this tutorial, you'll find several such "Hello World" programs with different degrees of sophistication.
  145. Our first "Hello World" program writes the string "Hello World" into the log window. This window is usually open when Oberon/F is used as programming environment. Whether or not the log window is opened on startup of Oberon/F depends on the configuration, which the user can change to his liking, as described in the Oberon/F User's Guide.
  146. MODULE ObxText0;
  147.     IMPORT Out;
  148.     PROCEDURE Do*;
  149.     BEGIN
  150.         Out.String("Hello World"); Out.Ln
  151.     END Do;
  152. END ObxText0.
  153. In this first example program, we can see several properties of an Oberon program:
  154. An Oberon program consists of one or more modules, e.g. ObxText0.
  155. One module may use ("import") other modules, e.g. ObxText0 imports Out.
  156. Standard modules of Oberon can be imported by new modules, e.g. ObxText0 imports the library module Out.
  157. Procedures of another module are referenced as "module.procedure", e.g. Out.String or Out.Ln.
  158. Procedures which are accessible to other modules must be marked as "exported" (the "*" mark of ObxText0.Do).
  159. The behavior of a program is provided to the system simply as one or several exported procedures, e.g. ObxText0.Do.
  160. In order to toy around with the above example program, you can open a text window (actually a window containing a document containing a text view) by choosing New in the File menu. After you have typed in the above source text, you can compile the module by choosing Compile in the Dev menu. In the log window you can see whether compilation was successful.
  161. If no error message has appeared in the log, you can execute procedure ObxText0.Do by selecting the string "ObxText0.Do" (just type it in after the module's text, in the same window) and then choosing Execute in the Dev menu. As a result, "Hello World" is written to the log. You can repeat Execute as often as you like.
  162. In Oberon, an exported procedure like ObxText0.Do is called a command, if it should be called from the user interface, rather than from other modules (although this is also possible). Commands can be activated in various ways. In production software, commands are typically activated by selecting a menu entry or by clicking on a button.
  163. Since module Out is a useful tool, we want to give an overview over its facilities, so we can use it in later example programs. An overview over a module's facilities is called the module's definition. Usually, a module definition in Oberon is written like the module itself, but with all non-exported items left out, with the procedure bodies eliminated, without the export marks, and with the pseudo-keyword DEFINITION instead of MODULE. Note however, that this is merely a convention for documentation purposes, and that the compiler cannot use a definition as input. The Browser automatically generates a definition text out of a compiled module's symbol file (-> DevBrowser).
  164. Here is the definition of module ObxText0:
  165. DEFINITION ObxText0;
  166.     PROCEDURE Do;
  167. END ObxText0.
  168. The definition of module Out is given below.
  169. DEFINITION Out;
  170.     PROCEDURE Open;
  171.     PROCEDURE Char (ch: CHAR);
  172.     PROCEDURE Ln;
  173.     PROCEDURE Int (i, n: LONGINT);
  174.     PROCEDURE Real (x: REAL; n: INTEGER);
  175.     PROCEDURE String (str: ARRAY OF CHAR);
  176. END Out.
  177. Open opens a log window, if there isn't one open already. In our examples here, we assume the log to be open, and thus never call this procedure. The other procedures write characters, carriage returns, integers, reals, and strings into the log. The parameter n in the procedures Int and Real denotes the number of digits to be used in the number's representation. If it is smaller than necessary (e.g. zero), the minimal number of digits is used.
  178. Another example of the use of module Out is the following program, whose ObxText1.Sum command calculates the sum 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7:
  179. MODULE ObxText1;
  180.     IMPORT Out;
  181.     PROCEDURE Sum*;
  182.         VAR i, sum: INTEGER;
  183.     BEGIN
  184.         i := 1; sum := 0;
  185.         WHILE i <= 7 DO
  186.             sum := sum + i;
  187.             i := i + 1
  188.         END;
  189.         Out.String("the sum of 0..7 yields "); Out.Int(sum, 0); Out.Ln
  190.     END Sum;
  191. END ObxText1.
  192. Alas, this program and the first "Hello World" mix their outputs in one and the same window, the log. Thus we'll present another "Hello World" program which explicitly generates a text, inserts this text in a text view, and opens this text view in its own window; independent of the log window. For the time being, a "view" may simply be regarded as the contents of a window.
  193. A text is an abstract data type, i.e. a data type which hides its internal workings in the module in which it is defined (i.e. in module TextModels). A text contains a sequence of characters, plus character attributes like font or color. A text does not know how to display itself, this capability is embodied in a text view. A text view is also an abstract data type, defined in module TextViews.
  194. Our next program imports TextModels and TextViews, plus modules Views. Furthermore, module TextMappers is imported, which provides services to generate or to scan formatted text:
  195. MODULE ObxText2;
  196.     IMPORT Views, TextModels, TextMappers, TextViews;
  197.     PROCEDURE Do*;
  198.         VAR t: TextModels.Model; f: TextMappers.Formatter; v: TextViews.View;
  199.     BEGIN
  200.         t := TextModels.dir.New();    (* produce an empty text *)
  201.         f.ConnectTo(t);        (* open formatter on text *)
  202.         f.WriteString("Hello World"); f.WriteLn;    (* write string and line separator *)
  203.         v := TextViews.dir.New(t);    (* produce a view which displays text t *)
  204.         Views.OpenView(v)    (* open the view in a window *)
  205.     END Do;
  206. END ObxText2.
  207. For this example, we can make the following remarks:
  208. Texts and text views are produced by so-called directory objects, namely TextModels.dir and TextViews.dir.
  209. Directory objects are exported global variables which contain New functions.
  210. Directory objects allocate and initialize variables of abstract data types.
  211. TextMappers provides a data type called a Formatter, which can write formatted output into a text object.
  212. The text formatter provides formatting procedures like WriteString or WriteLn.
  213. The text formatter is connected to the text t.
  214. The view is opened in a window, whose contents can be edited, stored, or printed; just like any other window that contains a text.
  215. Generating text output is often useful, but seldom sufficient. Usually we need a way to pass one or several input values to a program, and often a text is a suitable input type (e.g. for the compiler).
  216. In Oberon/F, visible text is represented in a text data structure, and this data structure can be accessed, and used as input, by any command. This makes it possible to use text produced by one command as input for another command.
  217. For example, the Loaded
  218. Modules command in the Info menu generates a list of all loaded modules. This list is simply a text; it can be edited, stored, and printed just like any text typed in by the user.
  219. The menu item Interface in the same menu takes a text, and looks for a text selection. If it finds a selection, it reads the text in the selection (input!) and interprets it as the name of a module. If it finds a module with this name, it opens a window with the definition of this module. This interface browser is an example of how a command uses a text generated earlier as input, in order to produce a new text as output.
  220. We now want to take a closer look at how a command like  Interface gets access to the text produced by Loaded
  221. Modules. The following program shows what happens in essence:
  222. MODULE ObxText3;
  223.     IMPORT TextControllers, Out;
  224.     PROCEDURE Do*;
  225.         VAR c: TextControllers.Controller; beg, end: LONGINT;
  226.     BEGIN
  227.         c := TextControllers.Focus();    (* get the focus controller, if any *)
  228.         IF (c # NIL) & c.HasSelection() THEN    (* a text view with a selection must be focus*)
  229.             c.GetSelection(beg, end);
  230.             Out.String("beg = "); Out.Int(beg, 0); Out.Ln;
  231.             Out.String("end = "); Out.Int(end, 0); Out.Ln
  232.         ELSE
  233.             Out.String("no text focus or no selection"); Out.Ln
  234.         END
  235.     END Do;
  236. END ObxText3.
  237. Whenever a command needs the contents of a window as input, it must be able to get at the appropriate window, and from there to its contents. At any time, Oberon/F provides access to one window's contents, this window is called the focus window. In user interfaces which support overlapping windows, the focus window is usually the front window.
  238. Sometimes, the role of focus window is actually split between two windows: the front window and a target window. In this case, the target window is a document (e.g. a text document), and the front window a dialog. In the normal case however, the front window also acts as target window.
  239. Menu commands operate on the front window, while commands invoked through buttons in the front window operate on the target window.
  240. Picture a: Window which is both Front and Target Window
  241. Picture b :Target Window is still the same, but a Dialog has become Front Window
  242. A command implementation can get its appropriate focus by calling a suitable function, e.g. TextControllers.Focus as in the above example. This shows that there is another important module in the text subsystem: TextControllers. A text controller handles the interaction of the user with a text displayed in a text view.
  243. After having checked that the focus controller is not NIL and that there currently is a selection, ObxText3 writes the beginning and end positions of the selected text to the log. A text position denotes the left edge of a character, starting at position 0:
  244. Picture c Character Positions
  245. Now we want to write the contents of the selection to the log:
  246. MODULE ObxText4;
  247.     IMPORT TextModels, TextControllers, Out;
  248.     PROCEDURE Do*;
  249.         VAR c: TextControllers.Controller; beg, end: LONGINT;
  250.             r: TextModels.Reader; ch: CHAR;
  251.     BEGIN
  252.         c := TextControllers.Focus();    (* get the focus controller, if any *)
  253.         IF (c # NIL) & c.HasSelection() THEN    (* a text view with a selection must be focus*)
  254.             c.GetSelection(beg, end);
  255.             r := c.text.NewReader(NIL);    (* create a reader for the view's text *)
  256.             r.SetPos(beg);    (* position reader at beginning of selection *)
  257.             r.ReadChar(ch);    (* read first character of selection *)
  258.             WHILE (r.Pos() <= end) & ~r.eot DO    (* stop at end of selection and at end of text *)
  259.                 Out.Char(ch);
  260.                 r.ReadChar(ch)    (* read next character in selection *)
  261.             END;
  262.             Out.Ln
  263.         ELSE
  264.             Out.String("no text focus or no selection"); Out.Ln
  265.         END
  266.     END Do;
  267. END ObxText4.
  268. This program is the same as ObxText3, except for the parts in bold face, which are either new or changed.
  269. This example shows the use of a TextModels.Reader. A text reader is an object which implements an access path to a text. The text itself creates its readers. A reader has a position on its text, and provides procedures for positioning on the text (SetPos), for inquiring the reader's current position (Pos) and for reading characters (ReadChar). After every character read, the reader's position is incremented by one.
  270. Working with Forms
  271. Compound User Interface
  272. Forms are essential business tools. The graphical elements that appear in a form layout are sometimes called controls. Typically, most of the graphical elements are text fields, complemented by a variety of buttons.
  273. In contrast to most other development systems on the market today, Oberon/F treats dialogs and controls the same way as it does normal document components, they are just views that form part of a compound document. Consequently, dialogs are stored in the same file format as are all other Oberon/F documents. The result of such a unification of compound documents and user interface controls is sometimes called a compound user interface.
  274. As dialogs and controls both are regular views, they can be contained in any Oberon/F container. Dialogs and controls can for example appear in any text, as the following command button shows: 
  275. Forms are special containers which may contain any view at any place. In Picture d an example of a form is shown. It contains text, a logo and several controls.
  276. Controls
  277. Oberon/F comes with a set of standard controls. The following list describes all standard Oberon/F controls. Picture d shows a form containing some of them.
  278. - Command Button    A command button is used to execute an Oberon/F command or command sequence. Alternatively, it can be linked to a global variable of type PROCEDURE.
  279. - Field    A field displays the value of a global variable which may be a string, an integer or a floating-point number. The value of the variable can be changed through the field. A field can also be linked to a variable of type Dialog.Currency or to an extension of Dialog.Combo.
  280. - Check Box    A check box displays the value of a global Boolean variable. This value can be changed with the check box.
  281. - Radio Button    A radio button is active at a particular value of a global integer variable.
  282. - List Box    A list box allows to select one value out of a list of choices. It is linked to a variable whose type is an extension of Dialog.Enumeration.
  283. - Selection Box    A selection box allows to select a subset out of a set of choices. It is linked to a variable whose type is an extension of Dialog.Selection or Dialog.LargeSet.
  284. - Combo Box    A combo box is a text field whose contents can also be set over a pop-up menu. It is linked to a variable whose type is an extension of Dialog.Combo.
  285. - Date Field    A date field displays the date specified in a global variable of type Dates.Date.
  286. - Time Field    A time field displays the time specified in a global variable of type Dates.Time.
  287. - Color Field    A color field displays a color. It can be linked to variables of type Ports.Color or Dialog.Color.
  288. - UpDownField    This is a field linked to an integer variable. The value can also be changed through arrow keys.
  289. Additional controls can be implemented by third parties. An example is given in 
  290. ObxButtons
  291.  which is shown in Picture e.
  292. Layout Mode and Mask Mode
  293. Electronic forms are documents which can be manipulated in two special modes: in one mode, a form's layout is edited (layout mode). In layout mode, controls are passive: buttons can be selected, but not "pressed". No control can be made focus. In the other mode (mask mode), a form is used for data entry, usually in a dialog. In this mode, the form's layout cannot be changed anymore, but the controls become active, i.e. text can be entered in text fields, buttons can be pressed, etc. In mask mode, no control may be selected (see Picture d).
  294. Picture d: A dialog (in mask mode) with several controls
  295. Picture e A dialog (in layout mode) which contains three "ObxButtons" controls
  296. In fact, it is also possible to enable both focus and selection, or to disable both, giving rise to an editor mode and a viewer mode, respectively. For forms processing, these additional modes are not relevant, however.
  297. Auxiliary and Tool Windows
  298. In typical electronic business forms, a button that is depressed causes an action on the form's contents, e.g. the data entered into the form is stored in a data base and cleared thereafter. However, there is an important special case of electronic forms where a button's action does not operate on the contents of its own form, but on another document' contents, in another window. The data that such a form contains act as parameters to its commands (i.e. its buttons). For example, a "Find & Replace" dialog may contain a text field labeled "find" and a button labeled "Find Next". The text field contains the string to be searched, and the button activates the search command. The search command then operates not on the dialog contents (the front window), but on the contents of the window beneath the dialog (the target window).
  299. A dialog which acts as front but not simultaneously as target window is called a tool. A dialog which acts both as front and as target window is shown in an auxiliary window. Auxiliary windows can be recognized by the [ and ] brackets around their titles, e.g. "[ New Form ]". On some platforms, tool windows are denoted by angular brackets around their titles, e.g. "<<
  300. Replace
  301. Modal vs Non-Modal dialogs
  302. Oberon/F currently supports non-modal forms only, i.e. dialogs or data entry masks which don't block the user from doing something else while the form is open, e.g. looking up information about the task currently performed. This means that windows with forms can be put away anytime, to suspend the task for a while and to later resume it, by bringing the window to the top again.
  303. When working with non-modal dialogs, the procedure which opens the dialog cannot wait for results but rather has to return control to the system. As a consequence, state cannot be held in local variables, i.e. on the stack, but rather has to be extracted and managed explicitly in a separate stack-like data structure. 
  304. Dependencies between Dialogs and Programs
  305. Ideally, forms design should be done by a professional in the graphics design and human interface fields, and program design should be done by a professional in the software engineering field. Thus these distinct tasks should be dependent on each other as little as possible. Yet the software engineer's final program must be able to work with the various forms that the human interface designer has developed, so some dependencies obviously exist.
  306. These dependencies consist of three different parts: a program must manipulate data contained in a form, thus it must be able to get this data into its program variables; it must be able to put computed results residing in its program variables back into the form; and one of its procedures must be triggered when the user performs an interaction with a control.
  307. Oberon/F handles these dependencies in a very convenient way: The basic idea is that the programmer should be relieved from mundane tasks like transferring data between program variables and controls as much as possible. Rather, a control should be able to monitor a program variable directly. Thus, when the user edits the data in a control, the change is immediately reflected in the corresponding program variable, without the need for intervention by the programmer. The other aspects of the dependencies between the controls and a module are described later. We will first concentrate on the generation of dialogs.
  308. Form Generation
  309. If a programmer only wants to describe the contents of a dialog (and not design it), he combines all variables to be presented in a form in one Oberon record, called an interactor. In this case, the record corresponds to the form, and the record fields to individual controls. A benefit of this convention is that Oberon/F can automatically generate a form out of a suitably defined record declaration. For example, an integer field is by default translated into a text entry field which only accepts integer numbers, a procedure-typed record field is translated into a push button, etc. The automatic generation of a form is particularly convenient if a user interface is only temporary, e.g. for testing purposes. In essence, it is a rapid prototyping aid.
  310. Note that the controls in a dialog may correspond to variables in different modules and different records. The variables not necessarily need to be defined in an interactor, it could be unstructured global variables also. Furthermore, a dialog may contain non-control views which are not linked to global variables at all, e.g. bitmap views or text views (cf Picture d).
  311. Instead of starting with the program, forms may also be defined first, and the suitable program written later. It is also possible to later change the linking between controls and program variables; to change the type of a variable's control (e.g. a text entry field for numbers may be replaced by a scroll bar); to edit the form's layout whenever desired; or to add graphical elements which do not appear as variables in the program because their use is only for decoration (e.g. a company logo in a form, see Picture d).
  312. Remember that a form is stored in the standard Oberon/F compound document file format, and thus remains editable at any time. This is convenient e.g. when forms need to be translated from one (natural) language into another one, e.g. from English to German. In Oberon/F this is possible for someone who has no access to the program's source code, because no recompilation is necessary. This is in contrast to many other development systems which translate a form specification - even if generated using an interactive editor - into a program source text, which must be compiled and linked to the rest of the program. Such a source-code-generator approach can be considered obsolete today.
  313. The consistency of an Oberon/F program with its forms is checked whenever a form is loaded. A control which is inconsistent with the variable to which it is linked (e.g. because the variable has been removed) can still be displayed, but is made inactive. This load-time check is possible because Oberon/F contains an advanced meta-programming system which makes all necessary type information available at run-time.
  314. The following example demonstrates how a form can be generated automatically out of an interactor, and how a command may acquire its parameters through a dialog.
  315. MODULE ObxForms0;
  316.     IMPORT Out;
  317.         find*: RECORD
  318.             find*, replace*: ARRAY 32 OF CHAR;
  319.             ignoreCase*: BOOLEAN;
  320.             count*: LONGINT;
  321.             Find*, Change*, ChangeOne*, ChangeAll*: PROCEDURE
  322.         END;
  323.     PROCEDURE Find;
  324.     BEGIN
  325.         Out.String("find "); Out.String(find.find); Out.Ln
  326.     END Find;
  327.     PROCEDURE Change;
  328.     BEGIN
  329.         Out.String("change "); Out.String(find.find); Out.String(" to "); Out.String(find.replace);
  330.         Out.String(" and find next"); Out.Ln
  331.     END Change;
  332.     PROCEDURE ChangeOne;
  333.     BEGIN
  334.         Out.String("change "); Out.String(find.find); Out.String(" to "); Out.String(find.replace);
  335.         Out.String(" once"); Out.Ln
  336.     END ChangeOne;
  337.     PROCEDURE ChangeAll;
  338.     BEGIN
  339.         Out.String("change "); Out.String(find.find); Out.String(" to "); Out.String(find.replace);
  340.         Out.String(" count: "); Out.Int(find.count, 0); Out.Ln
  341.     END ChangeAll;
  342. BEGIN
  343.     find.Find := Find;
  344.     find.Change := Change; find.ChangeOne := ChangeOne; find.ChangeAll := ChangeAll
  345. END ObxForms0.
  346. This example demonstrates the following:
  347. - A variable find is declared as record-typed global variable.
  348. - For every procedure-typed record field there is a parameterless global procedure
  349. - In the module's initialization part the procedure-typed fields of variable find are assigned
  350. - The commands use the content of the fields as parameters
  351. Picture f: "New Form" Dialog
  352. You can try out this example in the following way: invoke New
  353. Form... in the Dev menu, which opens the standard form generation dialog (see Picture f). In this dialog, type in  "ObxForms0.find" in the link field, then click on the command button. As a result, a dialog is generated and opened (cf. Picture g). Open this dialog as an auxiliary window (use the command "Open As Aux Dialog" in the Dev menu), click on the various buttons, fill out the text fields, and look at the output in the log window. Also try to enter a name into the count field which is linked to the LONGINT variable ObxForms0.find.count.
  354. Picture g:  Automatically Generated "Find & Replace" Dialog
  355. Obviously, ObxForms0 does not actually implement a find & replace algorithm, but merely writes to the log. However, this is sufficient as a demonstration.
  356. A newly generated form is always opened in layout mode, i.e. the form elements can be moved around, resized (cf. Picture h), and their properties may be modified using a special inspector tool. After editing, the form can be opened as a tool or as an auxiliary, where it can be used. To do that, execute Open
  357. Dialog or Open As Aux Dialog in the Dev menu. In this way, you get a window in layout mode simultaneously with another one in mask mode, for the same form, as in the example below:
  358. Picture h : Layout and Mask for the "Find & Replace" Dialog
  359. After editing, the form can be saved as a Oberon/F document. Such documents can be opened with the command StdCmds.OpenAuxDialog or StdCmds.OpenToolDialog to open an auxiliary or a tool dialog. Both commands take two parameters, a file specification of an Oberon/F document and a window title. The following two examples open the "New Form" dialog (an auxiliary) and the "Find" dialog (a tool).
  360.  "StdCmds.OpenAuxDialog('Form/Rsrc/New', 'New Form')"
  361.  "StdCmds.OpenToolDialog('Text/Rsrc/Find', 'Find')"
  362. Such command sequences are usually put into the menu definition text.
  363. Inspector
  364. The properties of a control can be inspected and modified with the control property inspector. On Windows, this inspector can be opened with the  Properties... command in submenu Object of menu Edit; on the Mac OS the command Component Info... in menu Edit opens the property inspector.
  365. The link field displays the variable to which the control is linked. In case of a command button, the link field may also contain an Oberon/F command sequence to be executed when the button is pressed. The label field can be used to set the label of a command button, a radio button, a check box or a caption.
  366. The level field is used for radio buttons. A radio button is active if the value of the integer variable it is linked to is equal to the level value. If a radio button is pressed, the value of the integer is set to the level value. Several radio buttons can be linked to the same integer variable, each with another level value. This guarantees that at most one of the radio buttons is active at the same time.
  367. A command button can be set to be a default or a cancel button. Default buttons in a form can be activated with the return key, and a cancel button can be "pressed" with the escape key.
  368. Picture i: Control Inspector with the Properties of the "Find" Command Button
  369. Updating Controls from within Commands
  370. While an interactive modification of a control is automatically reflected in the program variable, the opposite is not true: after assigning a new value to a variable to which several controls are linked, Oberon/F must be notified of the change. This is done with the Dialog.Update procedure. In order to be able to use this procedure, the interactor must be defined as a subtype of Dialog.Interactor, the type of the argument of Dialog.Update.
  371. Our next example program is a variation of an example shown in the command tutorial (ObxText1) which computes the sum of the integer from 1 up to a given limit. In contrast to the older version, the new version allows to enter the upper limit of the sum to be calculated, using a form. The result is also displayed in the form. The loop in procedure Sum is used for demonstration purposes only. Obviously, the formula of Gauss could be used to compute the sum without any loop.
  372. MODULE ObxForms1;
  373.     IMPORT Dialog, Out;
  374.         para*: RECORD (Dialog.Interactor)
  375.             limit*, sum*: LONGINT;
  376.             Sum*: PROCEDURE
  377.         END;
  378.     PROCEDURE Sum*;
  379.         VAR i, sum: LONGINT;
  380.     BEGIN
  381.         i := 1; sum := 0;
  382.         WHILE i <= para.limit DO
  383.             sum := sum + i;
  384.             i := i + 1
  385.         END;
  386.         para.sum := sum; Dialog.Update(para)
  387.     END Sum;
  388. BEGIN
  389.     para.Sum := Sum
  390. END ObxForms1.
  391. The form can be generated and opened with the following command.
  392.  "StdCmds.OpenToolDialog('Obx/Rsrc/Para', 'Tiny Calculator')"
  393. The user can enter a number in the limit control. This automatically updates the value of ObxForms1.para.limit. When the user clicks the Sum button (or presses the return key), the sum of 0 .. ObxForms1.para.limit is calculated and the ObxForms1.para.sum field is set to this sum and the dialog is updated.
  394. Note that the result is wrong if the limit is larger than 65535 due to integer overflow. If we want to prevent this from happening, then we need to program a notifier.
  395. Notifiers
  396. Notifiers are called whenever the value of a control is changed. A notifier may be used to react as soon as the value of a control has changed. A dialog can so be kept consistent. In our summation example we could compute the sum whenever the limit field is changed. The Sum button would then no longer be necessary. Additionally, we can assert that the user only enters values which are valid.
  397. MODULE ObxForms2;
  398.     IMPORT Dialog, Out;
  399.         para*: RECORD (Dialog.Interactor)
  400.             limit*, sum*: LONGINT;
  401.         END;
  402.     PROCEDURE NotifyLimit*(op, from, to: LONGINT);
  403.         VAR i, sum: LONGINT;
  404.     BEGIN
  405.         IF op = Dialog.changed THEN
  406.             IF para.limit > 65535THEN
  407.                 Dialog.Beep; para.limit := from
  408.             ELSE
  409.                 i := 1; sum := 0;
  410.                 WHILE i <= para.limit DO
  411.                     sum := sum + i;
  412.                     i := i + 1
  413.                 END;
  414.                 para.sum := sum
  415.             END;
  416.             Dialog.Update(para)
  417.         END
  418.     END NotifyLimit;
  419. END ObxForms2.
  420. The Dialog now has to be generated with the command New Form ... in menu Dev and the value of the notify field of the limit field has to be set to ObxForms2.NotifyLimit. Then the dialog can be used. Try it!
  421. Guards
  422. A control guard command is a procedure with the signature PROCEDURE (VAR par: Dialog.Par). It can set the fields of the par parameter to suitable values, e.g. a guard can define whether a control is read-only, whether it is disabled or undefined. Guard commands are called to determine the current state of a control, in particular to find out whether the control is enabled. For example, the Create button in the New Form dialog is only enabled if the link field is not empty.
  423. The guard commands are specified in the inspector dialog. Control guard commands are called after the user interactively changed the state of a control, or after a program calls the procedure Dialog.CheckGuards.
  424. Our last version of the summation example defines a read-only guard which is used for the sum field. A read-only sum field is guaranteed to be consistent, i.e. the user can only change the limit field which automatically adjusts the sum field.
  425. MODULE ObxForms3;
  426.     IMPORT Dialog, Out;
  427.         para*: RECORD (Dialog.Interactor)
  428.             limit*, sum*: LONGINT;
  429.         END;
  430.     PROCEDURE NotifyLimit* (op, from, to: LONGINT);
  431.         VAR i, sum: LONGINT;
  432.     BEGIN
  433.         IF op = Dialog.changed THEN
  434.             IF para.limit > 65535 THEN
  435.                 Dialog.Beep; para.limit := from
  436.             ELSE
  437.                 i := 1; sum := 0;
  438.                 WHILE i <= para.limit DO
  439.                     sum := sum + i;
  440.                     i := i + 1
  441.                 END;
  442.                 para.sum := sum
  443.             END;
  444.             Dialog.Update(para)
  445.         END
  446.     END NotifyLimit;
  447.     PROCEDURE ReadOnlyGuard* (VAR par: Dialog.Par);
  448.     BEGIN
  449.         par.readOnly := TRUE
  450.     END ReadOnlyGuard;
  451. END ObxForms3.
  452. Try out the module with the following embedded dialog (an embedded form view which contains two fields):
  453. You might also use the following embedded text fields. Both fields are linked to the interactor ObxForms3.para, and the notifier of the left field is set to ObxForms3.NotifyLimit. If you change the value in the left field, the right field is automatically updated. If you only update the left field, the sentence is consistent.
  454. The sum of integers from 1 to  
  455.    is  
  456. Working with Commands
  457. In Oberon, an exported procedure to be called by the user is called a command. In any Oberon environment, the user should be able to call a command, given the command's name and the name of the module that contains the command. A command is the atomic action that a user can execute, which means that commands may only be executed sequentially, not concurrently.
  458. A command always performs two tasks: first, it assembles the parameters that it needs. These parameters can be any part of the whole Oberon environment's global state. If a command uses global state about which the user does not know, the command becomes difficult to use, since its effect becomes unpredictable from a user's point of view. Such hidden modes should be avoided in good user interface designs, thus commands should only depend on state which is somehow visible to the user, e.g. the focus, its selection, its caret, etc.
  459. After the command's parameters have been gathered, an action is invoked with these parameters, e.g. closing a window, deleting selected text, or storing a form's contents into a database.
  460. For the user, there may be several ways to call a command. One way might be to press a button in a form, another way may be the activation of a text stretch which has the form "module.procedure". However, the most typical way is to select a menu item.
  461. After the core of Oberon/F has been started up, it tries to find a text document with file name Menus. This text contains the initial menu configuration. An example may look as follows:
  462. MENU "Info"
  463.     "Open Log"    ""    "DevCmds.OpenLog"    ""
  464.     "Clear Log"    "Y"    "DevCmds.ClearLog"    ""
  465.     SEPARATOR
  466.     "Loaded Modules"    ""    "DevDebug.ShowLoadedModules"    ""
  467.     "Global Variables"    ""    "DevDebug.ShowGlobalVariables"    "TextCmds.SelectionGuard"
  468.     "View State"    ""    "DevDebug.ShowViewState"    "StdCmds.SingletonGuard"
  469.     SEPARATOR
  470.     "Source"    ""    "DevReferences.ShowSource"    "TextCmds.SelectionGuard"
  471.     "Interface"    "D"    "DevBrowser.ShowInterface"    "TextCmds.SelectionGuard"
  472.     "Documentation"    ""    "DevReferences.ShowDocu"    "TextCmds.SelectionGuard"
  473.     SEPARATOR
  474.     "Menus"    ""    "StdMenuTool.ShowMenuTool"    ""
  475.     "Update Menus"    ""    "StdMenuTool.UpdateMenus"    "TextCmds.FocusGuard"
  476. MENU "Dev"
  477.     "New Form..."    ""    "StdCmds.OpenAuxDialog('Form/Rsrc/new', 'New Form')"    ""
  478.     "Open As Tool Dialog"    ""    "HostCmds.OpenAsToolDialog"    "HostCmds.ContainerGuard"
  479.     "Open As Aux Dialog"    ""    "HostCmds.OpenAsAuxDialog"    "HostCmds.ContainerGuard"
  480.     SEPARATOR
  481.     "Open Module List"    "0"    "DevCmds.OpenModuleList"    "TextCmds.SelectionGuard"
  482.     SEPARATOR
  483.     "Compile"    "K"    "DevCompiler.Compile"    "TextCmds.FocusGuard"
  484.     "Compile Selection"    ""    "DevCompiler.CompileSelection"    "TextCmds.SelectionGuard"
  485.     "Compile Module List"    ""    "DevCompiler.CompileModuleList"    "TextCmds.SelectionGuard"
  486.     SEPARATOR
  487.     "Unmark Errors"    ""    "DevMarkers.UnmarkErrors"    "TextCmds.FocusGuard"
  488.     "Next Error"    "E"    "DevMarkers.NextError"    "TextCmds.FocusGuard"
  489.     "Toggle Error Mark"    ";"    "DevMarkers.ToggleCurrent"    "TextCmds.FocusGuard"
  490.     SEPARATOR
  491.     "Execute"    ""    "DevDebug.Execute"    "TextCmds.SelectionGuard"
  492.     "Unload"    ""    "DevDebug.Unload"    "TextCmds.FocusGuard"
  493.     "Unload Module List"    ""    "DevDebug.UnloadModuleList"    "TextCmds.SelectionGuard"
  494.     "Flush Resources"    ""    "DevCmds.FlushResources"    ""
  495. MENU "Text" ("TextViews.ViewDesc")
  496.     "Find && Replace..."    "F"    "TextCmds.InitFindDialog; StdCmds.OpenToolDialog('Text/Rsrc/find', 'Find & Replace')"
  497.                 ""
  498.     "Find Again"    "G"    "TextCmds.FindAgain"    "TextCmds.FindAgainGuard"
  499.     SEPARATOR
  500.     "Shift Left"    "["    "TextCmds.ShiftLeft"    "StdCmds.SelectionGuard"
  501.     "Shift Right"    "]"    "TextCmds.ShiftRight"    "StdCmds.SelectionGuard"
  502.     "Superscript"    ""    "TextCmds.Superscript"    "StdCmds.SelectionGuard"
  503.     "Subscript"    ""    "TextCmds.Subscript"    "StdCmds.SelectionGuard"
  504.     SEPARATOR
  505.     "Insert Paragraph"    "M"    "TextCmds.InsertParagraph"    ""
  506.     "Insert Ruler"    "J"    "TextCmds.InsertRuler"    ""
  507.     "Insert Soft-Hyphen"    "-"    "TextCmds.InsertSoftHyphen"    "StdCmds.PasteCharGuard
  508.     "Insert Non-Brk Hyphen"    ""    "TextCmds.InsertNBHyphen"    "StdCmds.PasteCharGuard"
  509.     "Insert Non-Brk Space"    ""    "TextCmds.InsertNBSpace"    "StdCmds.PasteCharGuard"
  510.     "Insert Digit Space"    ""    "TextCmds.InsertDigitSpace"    "StdCmds.PasteCharGuard"
  511.     "Toggle Marks"    "H"    "TextCmds.ToggleMarks"    "StdCmds.TextCmds.ToggleMarksGuard"
  512.     SEPARATOR
  513.     "Make Default Attributes"    ""    "TextCmds.MakeDefaultAttributes"    "StdCmds.SelectionGuard"
  514.     "Make Default Ruler"    ""    "TextCmds.MakeDefaultRuler"    "StdCmds.SelectionGuard"
  515. The syntax of this configuration text in EBNF (Extended Backus-Naur Form) is the following:
  516. config =    { menu }.
  517. menu =    MENU menuname [ "(" typename ")" ] EOL { item } END EOL.
  518. item =    (itemname shortcut command guard | SEPARATOR ) EOL.
  519. menuname, typename, itemname, shortcut, command, and guard are strings delimited by double quotes.
  520. typename must contain the name of a record type in the form "module.record". Commands and guards must contain Oberon command sequences.
  521. Menu definitions which specify a typename define context-sensitive menus. Such menus are only available if the focus view supports this context, i.e. returns this typename. A view may specify which context it supports by setting the type field of the Controllers.PollOpsMsg. For more information read the section  Controller Messages in the view tutorial.
  522. A menu consists of several items. An item is either a single menu entry, or a separator. Separators are used to visually group menu entries in a menu. A menu entry has a visible representation (the item's name, as a string), possibly a keyboard shortcut, a command, and a guard. When the menu entry is selected, the command described by the command string is executed.
  523. By editing the Menus text, menu items or whole menus can be deleted or added.
  524. Mac OS:
  525. The standard menus like File, Edit, Attributes, and Windows cannot be reconfigured, and thus do not appear in the configuration text.
  526. Note that several menus of the standard configuration have a type name after the keyword MENU, written in parentheses. If no such type name is given, the menu is always available. If a type name is given, the corresponding menu is context-sensitive, i.e. only available if the focus is of the given type. The text menu, for example, only appears when a text view is focused.
  527. Menus can be changed at run-time: create the menu text with the Menus command in the Info menu. Then add e.g. the following item to the Dev menu:
  528.   "Insert Clock"    ""    "StdClocks.Deposit; StdCmds.PasteView"    ""
  529. Now activate the Update
  530. Menus command in the Info menu to update the menus. Now you can execute the new command by selecting the appropriate menu entry. It attempts to paste a clock into the front window's document.
  531. If you want to make the new menu configuration permanent, save the menu text in the Rsrc subdirectory, which resides in the System subdirectory of Oberon/F.
  532. Working with Files
  533. In Oberon/F, files play a less central role than in most programming environments, because the programmer only works with files indirectly. You stand a good chance that you'll never use the file interface directly. For this reason, we don't recommend that you concentrate on files when learning about Oberon/F, you should rather look at Oberon/F views (see the view tutorial) as the pivotal abstraction of the component framework. The only reasons for using files directly are the handling of legacy files produced with other software, and the implementation of simple file-based database systems.
  534. However, since files are important traditional abstractions in computer science, and form the basis of the persistent storage mechanism of Oberon/F, a short introduction is given anyway.
  535. Normally, data is written to a file ("externalized") and read from a file ("internalized") via so-called mapper objects, which perform the mapping of byte streams to higher-level data structures and vice versa. These mappers are provided by the framework where they are needed. Module Stores provides two such mappers, a Stores.Reader for internalization and a Stores.Writer for externalization. File mappers internally contain objects which connect them to files. These connecting objects are called file riders. There are two flavors of file riders, one for internalization and one for externalization.
  536. Picture j:  File with three Riders
  537. We'll first show how files can be accessed through direct use of riders, and then how they can be used via file mappers. The first of our examples demonstrates how an existing file is looked up, how its contents are copied to a new file, and how the new file is permanently registered in the file directory:
  538. MODULE ObxFiles0;
  539.     IMPORT Files;
  540.     PROCEDURE Do*;
  541.         VAR s: Files.Locator; old, new: Files.File; r: Files.Reader; w: Files.Writer;
  542.             ch: CHAR; res: LONGINT;
  543.     BEGIN
  544.         s := Files.dir.This("Obx");        (* get locator for Obx subdirectory *)
  545.         s := s.This("Samples");        (* get locator for Samples subdirectory *)
  546.         old := Files.dir.Old(s, "Test0", Files.shared);        (* look up file "Test0" *)
  547.         ASSERT(old # NIL, 20);        (* abort program if file not found *)
  548.         new := Files.dir.New(s);        (* create a new file at same location*)
  549.         r := old.NewReader(NIL); r.SetPos(0);        (* create a rider for internalization *)
  550.         w := new.NewWriter(NIL); w.SetPos(0);        (* create a rider for externalization *)
  551.         r.ReadByte(ch);
  552.         WHILE ~r.eof DO            (* stop when end of file is reached *)
  553.             w.WriteByte(ch);
  554.             r.ReadByte(ch)
  555.         END;
  556.         new.Register("Test1", old.type, res)        (* register copy as "Test1" *)
  557.     END Do;
  558. END ObxFiles0.
  559. In this program, a file is looked up in three steps. In the first step, the file directory object Files.dir delivers a locator for subdirectory Obx. All other file directory operations take such a locator as parameter. In the second step, a locator for directory Samples in Obx is created, i.e. of directory Obx/Samples. In the third step, the directory delivers a file object, provided a file with the given name exists. Files may be opened in shared mode or in exclusive mode. Usually, shared mode is used, which means that several invocations of Files.dir.Old for the same file yield the same file object; no one may alter the contents of this file.
  560. If the file's contents should be altered, a new file is generated instead. After the new file has been written, it replaces the old one. This replacement process is called registration. Registration is atomic, i.e. it either finishes successfully or it is aborted completely, but it will never destroy the contents of the old file during an abort. In the above example, the file is saved at the same location and with the same file type, but under another name.
  561. ObxFiles0 copies the contents of an old file to a newly created file. For that purpose, the old file produces a Files.Reader, and the new file produces a Files.Writer. These are the file riders mentioned earlier. They provide procedures to read, respectively to write, bytes, in the form of one-byte characters. Before they are used, their current positions must be set up (using their SetPos procedures). When a file reader attempts to read beyond the end of the file, it sets its eof flag to indicate this situation.
  562. ObxFiles0 shows another facility of the language Oberon: the ASSERT statement. An ASSERT statement checks a Boolean condition. If this condition is TRUE, it does nothing. If the condition is FALSE however, it aborts the program and gives an error message. The second parameter of an ASSERT, which must be an integer constant in the range 0..127, is displayed as part of the error message.
  563. Oberon/F makes extensive use of ASSERT statements, in particular to make sure that its procedures are called with legal parameters. For example, the procedure Files.dir.Old asserts that its locator parameter is not NIL, otherwise error 20 occurs.
  564. Apart from helping to detect errors early, assertions also are valuable as documentation of a program's properties. They are particularly helpful during design iterations and maintenance of a program.
  565. Assertions which test for legal input state of a procedure are called precondition assertions. An assertion checking the outcome of a procedure is called a postcondition assertion. Assertions in the middle of a procedure, to check against errors in it, are called invariant assertions. Precondition assertions are the most important of these assertion types in Oberon/F. As a convention, all three types have their own assertion number ranges, as given below. The first range (0..19) is free for your use, typically as breakpoints using the HALT statement.
  566. Free    
  567. 0 .. 
  568. Preconditions    
  569. 20 .. 
  570. Postconditions    
  571. 60 .. 
  572. Invariants    100 .. 120
  573. Reserved    121 .. 125
  574. Not Yet Implemented             126
  575. Interface Procedure Called             127
  576. When an assertion error (or halt) occurs, a window is opened with the so-called debug trap text. Among other things, it displays the assertion number and the name of the procedure where this assertion was violated. You can use this information to look up what was wrong in the on-line reference information.
  577. The following program is another example of how file riders and the end-of-file flag can be used. This example module could be used as a library module, i.e. a module which provides a generally useful service to other modules, but which is no complete application of its own:
  578. MODULE ObxFiles1;
  579.     IMPORT Files;
  580.     PROCEDURE Merge* (a, b: Files.Reader; c: Files.Writer);
  581.     (* merge two file into a new one, e.g.
  582.         first file = "ADFX" merged with
  583.         second file = "BGPXZ" yields
  584.         new file = "ABDFGPXXZ"
  585.         VAR inA, inB: CHAR;
  586.     BEGIN
  587.         a.ReadByte(inA);
  588.         b.ReadByte(inB);
  589.         WHILE ~a.eof & ~b.eof DO    (* stop when one or both files have been read *)
  590.             IF inA < inB THEN    (* choose smaller input *)
  591.                 c.WriteByte(inA); a.ReadByte(inA)
  592.             ELSE
  593.                 c.WriteByte(inB); b.ReadByte(inB)
  594.             END
  595.         END;
  596.         WHILE ~a.eof DO c.WriteByte(inA); a.ReadByte(inA) END;
  597.         WHILE ~b.eof DO c.WriteByte(inB); b.ReadByte(inB) END
  598.     END Merge;
  599. END ObxFiles1.
  600. The next example illustrates how data can be written to a file, and how it can be retrieved from the file again. As with all typical applications, mappers are used instead of riders. A file mapper (either a reader or a writer) provides input/output routines for the basic Oberon data types and for character arrays (strings):
  601. MODULE ObxFiles2;
  602.     IMPORT Files, Stores;
  603.     PROCEDURE Write* (a: INTEGER; s: ARRAY OF CHAR; b: BOOLEAN);
  604.         VAR loc: Files.Locator; f: Files.File; w: Stores.Writer; res: LONGINT;
  605.     BEGIN
  606.         loc := Files.dir.This("");
  607.         f := Files.dir.New(loc);
  608.         w.ConnectTo(f);        (* set up mapper w on file f *)
  609.         w.WriteString(s);
  610.         w.WriteInt(a);
  611.         w.WriteBool(b);
  612.         f.Register("Test2", "", res)    (* should use a suitable file type here *)
  613.     END Write;
  614.     PROCEDURE Read* (VAR a: INTEGER; VAR s: ARRAY OF CHAR; VAR b: BOOLEAN);
  615.         VAR loc: Files.Locator; f: Files.File; r: Stores.Reader;
  616.     BEGIN
  617.         loc := Files.dir.This("");
  618.         f := Files.dir.Old(loc, "Test2", Files.shared); ASSERT(f # NIL, 20);
  619.         r.ConnectTo(f);        (* set up mapper on file f *)
  620.         r.ReadString(s);
  621.         r.ReadInt(a);
  622.         r.ReadBool(b)            (* a shareable file need not be closed, *)
  623.     END Read;
  624. END ObxFiles2.
  625. In contrast to the earlier examples, the next program shows how a file is opened exclusively and updated locally, i.e. no new file is generated and registered, but an old one is modified in place:
  626. MODULE ObxFiles3;
  627.     IMPORT Files, Stores;
  628.     PROCEDURE Update*;
  629.         VAR loc: Files.Locator; f: Files.File; w: Stores.Writer;
  630.     BEGIN
  631.         loc := Files.dir.This("Obx");
  632.         loc := loc.This("Samples");
  633.         f := Files.dir.Old(loc, "Test0", Files.exclusive);    (* access file Obx/Samples/Test0 *)
  634.         IF f # NIL THEN            (* file is found and not yet open *)
  635.             w.ConnectTo(f);
  636.             w.SetPos(4);
  637.             w.WriteString("abcde");    (* overwrite five characters *)
  638.             f.Close                (* exclusively opened files should be closed *)
  639.         END
  640.     END Update;
  641. END ObxFiles3.
  642. Note that if the file were opened in shared mode, the w.ConnectTo(f) call would have been aborted with a run-time error: a writer cannot be connected to a file opened as shareable, because this implies a read-only permission.
  643. TextControllers.StdCtrlDesc
  644. TextControllers.ControllerDesc
  645. Helvetica
  646. Documents.ControllerDesc
  647.